<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="Ugmk6" id="Ugmk6"><span data-lake-id="u841eff5a" id="u841eff5a">典型回答</span></h1>
  <p data-lake-id="u3e840ae7" id="u3e840ae7"><span data-lake-id="u4f1a26a6" id="u4f1a26a6">Java 中区分 API 和 SPI，通俗的讲：API 和 SPI 都是相对的概念，他们的差别只在语义上，API 直接被应用开发人员使用，SPI 被框架扩展人员使用。</span></p>
  <p data-lake-id="u8c619543" id="u8c619543"><span data-lake-id="u063918f1" id="u063918f1">​</span><br></p>
  <p data-lake-id="u825bf9b6" id="u825bf9b6"><span data-lake-id="ua91dfc9c" id="ua91dfc9c">API Application Programming Interface</span></p>
  <p data-lake-id="ud0418458" id="ud0418458"><span data-lake-id="u29696754" id="u29696754">​</span><br></p>
  <p data-lake-id="u4e5201ed" id="u4e5201ed"><span data-lake-id="u7b5dca7e" id="u7b5dca7e">API是一组定义了软件组件之间交互的规则和约定的接口。提供方来制定接口并完成对接口的不同实现，调用方只需要调用即可。</span></p>
  <p data-lake-id="u58ac5a7e" id="u58ac5a7e"><span data-lake-id="udd9f0b7a" id="udd9f0b7a">​</span><br></p>
  <p data-lake-id="ue0a54770" id="ue0a54770"><span data-lake-id="ufd2e6f8e" id="ufd2e6f8e">SPI Service Provider Interface</span></p>
  <p data-lake-id="uc3c2486a" id="uc3c2486a"><span data-lake-id="ubebb609e" id="ubebb609e">​</span><br></p>
  <p data-lake-id="u46bcdef5" id="u46bcdef5"><span data-lake-id="u19fb7377" id="u19fb7377">SPI是一种扩展机制，通常用于在应用程序中提供可插拔的实现。 调用方可选择使用提供方提供的内置实现，也可以自己实现。</span></p>
  <p data-lake-id="u2af9a30f" id="u2af9a30f"><span data-lake-id="ua41fb57b" id="ua41fb57b">​</span><br></p>
  <p data-lake-id="u3084efbf" id="u3084efbf"><span data-lake-id="ua21f5fe2" id="ua21f5fe2">请记住这句话：</span><strong><span data-lake-id="u3fccaecd" id="u3fccaecd">API用于定义调用接口，而SPI用于定义和提供可插拔的实现方式。</span></strong></p>
  <h1 data-lake-id="iy2q5" id="iy2q5"><span data-lake-id="u2c353c3a" id="u2c353c3a">知识扩展</span></h1>
  <h2 data-lake-id="peQat" id="peQat"><span data-lake-id="u68b60f1e" id="u68b60f1e">如何定义一个SPI</span></h2>
  <p data-lake-id="ubbb19f98" id="ubbb19f98"><span data-lake-id="u8368e924" id="u8368e924">步骤1、定义一组接口 (假设是org.foo.demo.IShout)，并写出接口的一个或多个实现，(假设是org.foo.demo.animal.Dog、org.foo.demo.animal.Cat)。</span></p>
  <pre lang="java"><code>
public interface IShout {
    void shout();
}
public class Cat implements IShout {
    @Override
    public void shout() {
    	System.out.println("miao miao");
	}
}
public class Dog implements IShout {
    @Override
    public void shout() {
    	System.out.println("wang wang");
    }
}
</code></pre>
  <p data-lake-id="u7cc403af" id="u7cc403af"><span data-lake-id="u929c0391" id="u929c0391">步骤2、在 src/main/resources/ 下建立 /META-INF/services 目录， 新增一个以接口命名的文件 (org.foo.demo.IShout文件)，内容是要应用的实现类（这里是org.foo.demo.animal.Dog和org.foo.demo.animal.Cat，每行一个类）。</span></p>
  <p data-lake-id="u727a36fc" id="u727a36fc"><span data-lake-id="u1265b9ec" id="u1265b9ec">org.foo.demo.animal.Dog</span></p>
  <p data-lake-id="uf8908d1e" id="uf8908d1e"><span data-lake-id="u77a71e57" id="u77a71e57">org.foo.demo.animal.Cat</span></p>
  <p data-lake-id="u1d979271" id="u1d979271"><span data-lake-id="ubb248937" id="ubb248937">步骤3、使用 ServiceLoader 来加载配置文件中指定的实现。</span></p>
  <pre lang="java"><code>
public class SPIMain {
    public static void main(String[] args) {
        ServiceLoader&lt;IShout&gt; shouts = ServiceLoader.load(IShout.class);
        for (IShout s : shouts) {
        	s.shout();
        }
    }
}
</code></pre>
  <p data-lake-id="u087a384f" id="u087a384f"><span data-lake-id="u77d65434" id="u77d65434">代码输出：</span></p>
  <p data-lake-id="u811e96ef" id="u811e96ef"><span data-lake-id="u67a97c77" id="u67a97c77">wang wang</span></p>
  <p data-lake-id="u391fa215" id="u391fa215"><span data-lake-id="udd071355" id="udd071355">miao miao</span></p>
  <h2 data-lake-id="RoXRC" id="RoXRC"><span data-lake-id="u47186468" id="u47186468">SPI的实现原理</span></h2>
  <p data-lake-id="u3a76b055" id="u3a76b055"><span data-lake-id="u28af346f" id="u28af346f">看ServiceLoader类的签名类的成员变量：</span></p>
  <pre lang="java"><code>
public final class ServiceLoader&lt;S&gt; implements Iterable&lt;S&gt;{
    private static final String PREFIX = "META-INF/services/";
    // 代表被加载的类或者接口
    private final Class&lt;S&gt; service;
    // 用于定位，加载和实例化providers的类加载器
    private final ClassLoader loader;
    // 创建ServiceLoader时采用的访问控制上下文
    private final AccessControlContext acc;
    // 缓存providers，按实例化的顺序排列
    private LinkedHashMap&lt;String,S&gt; providers = new LinkedHashMap&lt;&gt;();
    // 懒查找迭代器
    private LazyIterator lookupIterator;
    ......
}
</code></pre>
  <p data-lake-id="u2486df91" id="u2486df91"><span data-lake-id="ub342b585" id="ub342b585">参考具体源码，梳理了一下，实现的流程如下：</span></p>
  <ol list="u7c5f7b03">
   <li fid="u685577b2" data-lake-id="uff54be39" id="uff54be39"><span data-lake-id="ub5dd2ca0" id="ub5dd2ca0">应用程序调用ServiceLoader.load方法，ServiceLoader.load方法内先创建一个新的ServiceLoader，并实例化该类中的成员变量，包括：</span></li>
  </ol>
  <ol list="u7c5f7b03" data-lake-indent="1">
   <li fid="u685577b2" data-lake-id="ub28a4616" id="ub28a4616"><span data-lake-id="udfa6a351" id="udfa6a351">loader(ClassLoader类型，类加载器)</span></li>
   <li fid="u685577b2" data-lake-id="u4fef5c64" id="u4fef5c64"><span data-lake-id="u49338313" id="u49338313">acc(AccessControlContext类型，访问控制器)</span></li>
   <li fid="u685577b2" data-lake-id="u7aa75457" id="u7aa75457"><span data-lake-id="uee445a5d" id="uee445a5d">providers(LinkedHashMap类型，用于缓存加载成功的类)</span></li>
   <li fid="u685577b2" data-lake-id="u56a588ff" id="u56a588ff"><span data-lake-id="u7367c96a" id="u7367c96a">lookupIterator(实现迭代器功能)</span></li>
  </ol>
  <ol list="u7c5f7b03" start="2">
   <li fid="u685577b2" data-lake-id="ub76fc434" id="ub76fc434"><span data-lake-id="u783c0b03" id="u783c0b03">应用程序通过迭代器接口获取对象实例，</span></li>
  </ol>
  <ol list="u7c5f7b03" data-lake-indent="1">
   <li fid="u685577b2" data-lake-id="u730aa8d8" id="u730aa8d8"><span data-lake-id="ubdf62c38" id="ubdf62c38">ServiceLoader先判断成员变量providers对象中(LinkedHashMap类型)是否有缓存实例对象，如果有缓存，直接返回。</span></li>
   <li fid="u685577b2" data-lake-id="ubd6afba1" id="ubd6afba1"><span data-lake-id="ub64382df" id="ub64382df">如果没有缓存，执行类的装载：</span></li>
  </ol>
  <ol list="u7c5f7b03" data-lake-indent="2">
   <li fid="u685577b2" data-lake-id="u26550e1b" id="u26550e1b"><span data-lake-id="u7f8e1f31" id="u7f8e1f31">读取META-INF/services/下的配置文件，获得所有能被实例化的类的名称</span></li>
   <li fid="u685577b2" data-lake-id="uef31546b" id="uef31546b"><span data-lake-id="ued9c2a6b" id="ued9c2a6b">通过反射方法Class.forName()加载类对象，并用instance()方法将类实例化</span></li>
   <li fid="u685577b2" data-lake-id="ub9de6ce8" id="ub9de6ce8"><span data-lake-id="u87001477" id="u87001477">把实例化后的类缓存到providers对象中(LinkedHashMap类型）</span></li>
   <li fid="u685577b2" data-lake-id="uf932d5f3" id="uf932d5f3"><span data-lake-id="u1fbbc590" id="u1fbbc590">然后返回实例对象。</span></li>
  </ol>
  <h2 data-lake-id="YKdrC" id="YKdrC"><span data-lake-id="ue909dccb" id="ue909dccb">SPI的应用场景</span></h2>
  <p data-lake-id="uf6898fdf" id="uf6898fdf"><span data-lake-id="uab7e60ed" id="uab7e60ed">概括地说，适用于：调用者根据实际使用需要，启用、扩展、或者替换框架的实现策略。比较常见的例子：</span></p>
  <ol list="u3cfa17cb">
   <li fid="uec9a63b3" data-lake-id="u212c8bc4" id="u212c8bc4"><span data-lake-id="u0f2cb83b" id="u0f2cb83b">数据库驱动加载接口实现类的加载</span></li>
   <li fid="uec9a63b3" data-lake-id="ub4edc830" id="ub4edc830"><span data-lake-id="u2cce2484" id="u2cce2484">JDBC加载不同类型数据库的驱动</span></li>
   <li fid="uec9a63b3" data-lake-id="uc1ba8977" id="uc1ba8977"><span data-lake-id="ud5b4f3ac" id="ud5b4f3ac">日志门面接口实现类加载</span></li>
   <li fid="uec9a63b3" data-lake-id="uace81f2e" id="uace81f2e"><span data-lake-id="u292b9313" id="u292b9313">SLF4J加载不同提供商的日志实现类</span></li>
  </ol>
  <p data-lake-id="u21b670e0" id="u21b670e0"><span data-lake-id="uf73ef124" id="uf73ef124">​</span><br></p>
  <p data-lake-id="ud7349d48" id="ud7349d48"><strong><span data-lake-id="ue9e06468" id="ue9e06468">Spring</span></strong></p>
  <p data-lake-id="ucf22721e" id="ucf22721e"><span data-lake-id="u79c98aab" id="u79c98aab">Spring中大量使用了SPI,比如：对servlet3.0规范对ServletContainerInitializer的实现、自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等</span></p>
  <p data-lake-id="u86517c2a" id="u86517c2a"><span data-lake-id="uf1afbc31" id="uf1afbc31">​</span><br></p>
  <p data-lake-id="u4be84c79" id="u4be84c79"><strong><span data-lake-id="u5fff0543" id="u5fff0543">Dubbo</span></strong></p>
  <p data-lake-id="u217f2e51" id="u217f2e51"><span data-lake-id="ua17d9d95" id="ua17d9d95">Dubbo中也大量使用SPI的方式实现框架的扩展, 不过它对Java提供的原生SPI做了封装，允许用户扩展实现Filter接口</span></p>
 </body>
</html>